package com.uit.project.bustrip.dao;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONObject;

import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import com.uit.project.bustrip.daointerface.BaseDao;
import com.uit.project.bustrip.database.DBHelper;
import com.uit.project.bustrip.valueobject.QueryOption;

public class BaseDaoImpl<T,ID> implements BaseDao<T, ID> {
	/**
	 * Entity Class.
	 */
	private Class<T> entityClass;
	private Class<ID> idClass;
	/**
	 * Database Helper.
	 */
	private DBHelper dbHelper;
	@SuppressWarnings("unchecked")
	public BaseDaoImpl(){
		this.entityClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
		this.idClass = (Class<ID>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[1];
	}
	@SuppressWarnings("unchecked")
	public BaseDaoImpl(DBHelper db){
		this.setDbHelper(db);
		this.entityClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
		this.idClass = (Class<ID>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[1];
	}
	
	@Override
	public Class<T> getEntityClass() {
		return entityClass;
	}

	@Override
	public String getTableName() {
		return this.getEntityClass().getSimpleName();
	}

	@Override
	public List<T> findAll() {
		List<T> entities = new ArrayList<T>();
		Cursor c = this.getDbHelper().query(this.getAllQueryOption());
		if (c.moveToFirst()) {
			do {
				T entity = this.cursorToEntity(c);
				entities.add(entity);
			} while (c.moveToNext());
		}
		return entities;
	}
	@Override
	public List<T> find(QueryOption options) {
		List<T> entities = new ArrayList<T>();
		Cursor c = this.getDbHelper().query(options);
		if (c.moveToFirst()) {
			do {
				T entity = this.cursorToEntity(c);
				entities.add(entity);
			} while (c.moveToNext());
		}
		return entities;
	}

	@Override
	public T findById(ID id) {
		T entity = null;
		Cursor c = this.getDbHelper().query(this.getByIdQueryOption(id));
		if (c.moveToFirst()) {
			do {
				entity = this.cursorToEntity(c);
				if(entity != null)
					return entity;
			} while (c.moveToNext());
		}
		return entity;
	}

	@Override
	public long insert(T entity) {
		return this.getDbHelper().insert(this.getTableName(), null, this.getContentValues(entity));
	}

	@Override
	public int update(T entity) {
		return this.getDbHelper().update(this.getTableName(), this.getContentValues(entity), null, null);
	}

	@Override
	public int insertOrUpdate(T entity) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int delete(ID id) {
		String where = "id =?";
		String[] whereargs = {id.toString()};
		return this.getDbHelper().delete(this.getTableName(), where, whereargs);
	}

	@Override
	public int deleteAll() {
		return this.getDbHelper().delete(this.getTableName(), null, null);
	}

	@Override
	public int deleteBy(String whereClause, String[] whereArgs) {
		// TODO Auto-generated method stub
		return 0;
	}
	public ContentValues getContentValues(T entity) {
		ContentValues values = new ContentValues();
		Field[] fields = this.entityClass.getFields();
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			try {
				String name = f.getName();
				Class<?> clazz = f.getType();
				if(clazz == Integer.class){
					values.put(name,(Integer)f.get(entity));	
				}else if(clazz == String.class){
					values.put(name,(String)f.get(entity));
				}else if(clazz == Double.class){
					values.put(name,(Double)f.get(entity));
				}else if(clazz == Float.class){
					values.put(name,(Float)f.get(entity));
				}else if(clazz == Short.class){
					values.put(name,(Short)f.get(entity));
				}else if(clazz == Boolean.class){
					values.put(name,(Boolean)f.get(entity));
				}else if(clazz == Byte.class){
					values.put(name,(Byte)f.get(entity));
				}else if(clazz == byte[].class){
					values.put(name,(byte[])f.get(entity));
				}
			} catch (IllegalArgumentException e) {
				Log.e("IllegalArgumentException", e.getMessage());
				return null;
			} catch (IllegalAccessException e) {
				Log.e("IllegalAccessException", e.getMessage());
				return null;
			}
		}
		return values;
	}
	public QueryOption getAllQueryOption() {
		QueryOption q = new QueryOption();
		q.setTable(this.getTableName());
		Field[] fields = this.entityClass.getFields();
		int size = fields.length;
		String[] columns = new String[size];
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			try {
				columns[i] = f.getName();
			} catch (Exception e) {
				Log.e("Class Reflect ", "Error get Field name ");
			}
		}
		q.setColumns(columns);
		q.setGroupBy(null);
		q.setHaving(null);
		q.setLimit(null);
		q.setOrderBy(null);
		q.setSelection(null);
		q.setSelectionArgs(null);
		return q;
	}
	public QueryOption getByIdQueryOption(ID id){
		QueryOption q = new QueryOption();
		q.setTable(this.getTableName());
		Field[] fields = this.entityClass.getFields();
		int size = fields.length;
		String[] columns = new String[size];
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			try {
				columns[i] = f.getName();
			} catch (Exception e) {
				Log.e("Class Reflect ", "Error get Field name ");
			}
		}
		q.setColumns(columns);
		q.setGroupBy(null);
		q.setHaving(null);
		q.setLimit(null);
		q.setOrderBy(null);
		String selection = "";
		String[] idColumns = null;
		if(this.idClass == Integer.class || this.idClass == String.class || this.idClass == Float.class){
			selection = "id=?";
			idColumns = new String[1];
			idColumns[0] = id.toString();
		}else{
			Field[] idFields = this.idClass.getFields();
			int idSize = idFields.length;
			idColumns = new String[idSize];
			for (int i = 0; i < idColumns.length; i++) {
				Field f = idFields[i];
				try {
					idColumns[i] = (String) f.get(id);
					if(i == 0){
						selection += f.getName() + "=?";
					}
					else{
						selection += "and " + f.getName() + "=?";
					}
				} catch (Exception e) {
					Log.e("Class Reflect ", "Error get Field name ");
				}
			}
		}
		q.setSelection(selection);
		q.setSelectionArgs(idColumns);
		return q;
	}
	public T cursorToEntity(Cursor c){
		T obj = null;
		try {
			obj = this.entityClass.newInstance();
			Field[] fields = this.entityClass.getFields();
			for (int i = 0; i < fields.length; i++) {
				Field f = fields[i];
				Class<?> clazz = f.getType();
				if(clazz == Integer.class){
					f.set(obj, c.getInt(i));
				}else if(clazz == String.class){
					f.set(obj, c.getString(i));
				}else if(clazz == Double.class){
					f.set(obj, c.getDouble(i));
				}else if(clazz == Float.class){
					f.set(obj, c.getFloat(i));
				}else if(clazz == Short.class){
					f.set(obj, c.getShort(i));
				}else if(clazz == Boolean.class){
					int b = c.getInt(i);
					Boolean cval = false;
					if(b == 1) cval = true;
					f.set(obj, cval);
				}else if(clazz == Byte.class){
					f.set(obj, c.getBlob(i));
				}else if(clazz == byte[].class){
					f.set(obj, c.getBlob(i));
				}
			}
		} catch (InstantiationException e) {
			Log.e("InstantiationException", e.getMessage());
			return null;
		} catch (IllegalAccessException e) {
			Log.e("IllegalAccessException", e.getMessage());
			return null;
		}
		return (T) obj;
	}

	@Override
	public int updateBy(T entity, String whereClause, String[] whereArgs) {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * @return the dbHelper
	 */
	public DBHelper getDbHelper() {
		return dbHelper;
	}

	/**
	 * @param dbHelper the dbHelper to set
	 */
	public void setDbHelper(DBHelper dbHelper) {
		this.dbHelper = dbHelper;
	}
	/**
	 * @return the idClass
	 */
	public Class<ID> getIdClass() {
		return idClass;
	}
	/**
	 * @param idClass the idClass to set
	 */
	public void setIdClass(Class<ID> idClass) {
		this.idClass = idClass;
	}
	@Override
	public T getFromJSON(JSONObject json) {
		return null;
	}
}
